<template>
  <div class="page">
    <!-- 类型栏 -->
    <el-card class="type-bar">
      <el-button
        v-for="(item, idx) in selectData.typeList"
        :key="idx"
        type="primary"
        plain
        @click="add(item.value)"
        >{{ item.name }}</el-button
      >
    </el-card>
    <!-- 主体 -->
    <el-card class="main">
      <!-- 工具栏 -->
      <div class="tool-bar">
        <el-button @click="exportDialogShow = true">导出</el-button>
        <!-- <el-button @click="importDialogShow = true">导入</el-button> -->
        <el-button @click="clean">清空</el-button>
      </div>
      <!-- 表单项 -->
      <div
        v-for="(fItem, fIdx) in formList"
        :key="'main-' + fIdx"
        @click="formSelect(fIdx)"
      >
        <el-card
          class="main-card"
          :class="{ active: currentFormIndex == fIdx }"
        >
          <div slot="header" class="formHeader">
            <div class="formTitle">
              <span>表单名：</span>
              <el-input v-model="fItem.functionName" />
            </div>
            <div class="tools">
              <!-- 导入 -->
              <el-button
                type="text"
                icon="el-icon-document-add"
                @click="importShow(fIdx)"
              ></el-button>
              <!-- 删除 -->
              <el-button
                type="text"
                icon="el-icon-delete"
                @click="delForm()"
              ></el-button>
            </div>
          </div>
          <el-form :form="fItem.jsonForm" class="main-form">
            <draggable v-model="fItem.jsonList" :group="{ name: 'people' }">
              <transition-group>
                <div
                  class="input-items"
                  :class="{
                    active: currentFormIndex == fIdx && currentIndex == idx,
                    'dst-block': field.className == 'dst-block',
                    'is-required': field.commonParams[4].value,
                  }"
                  v-for="(field, idx) in fItem.jsonList"
                  @click="handleSelectItem(field, idx, fIdx)"
                  :key="idx"
                >
                  <div class="flex">
                    <div class="label">{{ field.label }}</div>
                    <div class="input">
                      <!-- 输入框 -->
                      <template v-if="field.type == 'input'">
                        <el-input v-model="jsonForm[field.prop]"></el-input>
                      </template>
                      <!-- 下拉框 -->
                      <template v-if="field.type == 'select'">
                        <el-select v-model="jsonForm[field.prop]"></el-select>
                      </template>
                      <!-- 单选框 -->
                      <template v-if="field.type == 'radio'">
                        <el-radio v-model="jsonForm[field.prop]"></el-radio>
                      </template>
                      <!-- 多选框 -->
                      <template v-if="field.type == 'checkbox'">
                        <el-checkbox
                          v-model="jsonForm[field.prop]"
                        ></el-checkbox>
                      </template>
                      <!-- 级联框 -->
                      <template v-if="field.type == 'cascader'">
                        <el-cascader
                          v-model="jsonForm[field.prop]"
                        ></el-cascader>
                      </template>
                      <!-- 日期 -->
                      <template v-if="field.type == 'date'">
                        <el-date-picker
                          v-model="jsonForm[field.prop]"
                          type="date"
                          placeholder="选择日期"
                        >
                        </el-date-picker>
                      </template>
                      <!-- 日期范围 -->
                      <template v-if="field.type == 'daterange'">
                        <el-date-picker
                          v-model="jsonForm[field.prop]"
                          type="daterange"
                          range-separator="至"
                          start-placeholder="开始日期"
                          end-placeholder="结束日期"
                        >
                        </el-date-picker>
                      </template>
                      <!-- 文本域 -->
                      <template v-if="field.type == 'textarea'">
                        <el-input
                          type="textarea"
                          v-model="jsonForm[field.prop]"
                        ></el-input>
                      </template>
                      <!-- slot -->
                      <template v-if="field.type == 'slot'">
                        <div></div>
                      </template>
                      <!-- 上传 -->
                      <template v-if="field.type == 'dstUpload'">
                        <el-upload action="" v-model="jsonForm[field.prop]">
                          <el-button>上传</el-button>
                        </el-upload>
                      </template>
                    </div>
                  </div>
                  <!-- 操作栏 -->
                  <div class="tools" v-show="currentIndex == idx">
                    <i
                      class="el-icon-arrow-up"
                      @click.capture.stop="moveFn(idx, -1)"
                    ></i>
                    <i
                      class="el-icon-arrow-down"
                      @click.capture.stop="moveFn(idx, 1)"
                    ></i>
                    <i
                      class="el-icon-delete"
                      @click.capture.stop="delFn(fIdx, idx)"
                    ></i>
                  </div>
                </div>
              </transition-group>
            </draggable>
          </el-form>
        </el-card>
      </div>
      <el-button
        icon="el-icon-plus"
        type="info"
        circle
        @click="addFun"
      ></el-button>
    </el-card>
    <!-- 属性栏 -->
    <el-card class="property-bar">
      <template v-if="currentItem">
        <el-form :model="currentItem" label-width="120px" label-position="left">
          <el-tabs type="border-card">
            <el-tab-pane label="基础属性">
              <FormItem :list="currentItem.commonParams"></FormItem>
            </el-tab-pane>
            <el-tab-pane label="选项属性" v-if="optionsProperty">
              <FormItem :list="currentItem.selectProperty"></FormItem>
            </el-tab-pane>
            <el-tab-pane label="校验属性">
              <FormItem :list="currentItem.validateParams"></FormItem>
            </el-tab-pane>
            <el-tab-pane
              label="上传属性"
              v-if="currentItem.type == 'dstUpload'"
            >
              <FormItem :list="currentItem.uploadParams"></FormItem>
            </el-tab-pane>
          </el-tabs>
        </el-form>
      </template>
    </el-card>
    <!-- 导出弹窗 -->
    <el-dialog
      title="导出"
      width="500px"
      :visible="exportDialogShow"
      :before-close="handleCloseDialog"
    >
      <el-form :model="exportForm">
        <el-form-item label="输出目录">
          <el-input v-model="exportForm.dir" />
        </el-form-item>
        <el-form-item label="输出文件名">
          <el-input v-model="exportForm.fileName">
            <template slot="append">.js</template>
          </el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button class="" @click="handleCloseDialog()">取消</el-button>
        <el-button class="el-button--success" @click="exportFn">确定</el-button>
      </div>
    </el-dialog>
    <!-- 导入弹窗 -->
    <el-dialog
      title="导入"
      width="500px"
      :visible="importDialogShow"
      :before-close="handleCloseDialog"
    >
      <el-form :model="importForm">
        <el-form-item label="导入格式">
          <el-radio-group v-model="importForm.type">
            <el-radio label="yapi">yapi格式</el-radio>
            <!-- <el-radio label="dstForm">dstForm格式</el-radio> -->
          </el-radio-group>
        </el-form-item>
        <el-form-item label="导入内容">
          <el-input
            type="textarea"
            v-model="importForm.content"
            :rows="20"
          ></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button class="" @click="handleCloseDialog()">取消</el-button>
        <el-button class="el-button--success" @click="importJson"
          >确定</el-button
        >
      </div>
    </el-dialog>
  </div>
</template>

<script>
import cloneDeep from "lodash/cloneDeep";
import draggable from "vuedraggable";
import FormItem from "@/components/formItem";

import { check } from "@/utils/valid";

export default {
  name: "FormGen",
  components: {
    FormItem,
    draggable,
  },
  data() {
    return {
      valid: {
        check,
      },
      // 表单模块
      formList: [
        {
          functionName: "dataArr",
          jsonList: [], // 子表单
        },
      ],
      jsonForm: {},
      selectData: {
        //
        typeList: [
          { value: "input", name: "输入框 input" },
          { value: "textarea", name: "文本域 textarea" },
          { value: "select", name: "选择框 select" },
          { value: "radio", name: "单选框 radio" },
          { value: "checkbox", name: "多选框 checkbox" },
          { value: "cascader", name: "级联 cascader" },
          { value: "date", name: "日期选择器 date" },
          { value: "daterange", name: "日期范围选择器 daterange" },
          { value: "dstUpload", name: "上传 dstUpload" },
          { value: "slot", name: "插槽 slot" },
        ],
        // 校验方法
        validateList: [
          { name: "文本限制长度", value: "checkTitleLenth" },
          { name: "下拉框", value: "checkSelect" },
          { name: "限制数字区间", value: "rangeNumber" },
          { name: "手机号", value: "validatePhone" },
          { name: "手机号、电话带分机号", value: "validatePhoneTel" },
          { name: "邮箱", value: "validateEmail" },
          { name: "身份证", value: "validateIdCard" },
        ],
        // 文件类型
        fileTypes: [
          { name: "jpg", value: "jpg" },
          { name: "jpeg", value: "jpeg" },
          { name: "png", value: "png" },
          { name: "pdf", value: "pdf" },
          { name: "xlsx", value: "xlsx" },
          { name: "doc", value: "doc" },
          { name: "docx", value: "docx" },
          { name: "zip", value: "zip" },
        ],
      },
      currentFormIndex: 0, // 当前表单字段
      currentIndex: null, // 当前字段index
      // 导出弹窗
      exportDialogShow: false,
      exportForm: {
        dir: "",
        fileName: "",
      },
      // 导入弹窗
      importDialogShow: false,
      importForm: {
        type: "yapi",
        content: "",
      },
    };
  },
  computed: {
    currentForm({ currentFormIndex }) {
      return currentFormIndex !== null ? this.formList[currentFormIndex] : null;
    },
    currentItem({ currentForm, currentIndex }) {
      return currentForm && currentIndex !== null
        ? currentForm.jsonList[currentIndex]
        : null;
    },
    optionsProperty({ currentItem }) {
      return ["select", "cascader"].includes(currentItem.type);
    },
  },
  methods: {
    add(typeName) {
      this.currentForm.jsonList.push(this.generateItem({ typeName }));
    },
    addFun() {
      this.formList.push({
        functionName: "dataArr",
        jsonList: [],
      });
    },
    // 选中
    handleSelectItem(row, idx, fIdx) {
      this.currentFormIndex = fIdx;
      this.currentIndex = idx;
    },
    // 表单选中
    formSelect(fIdx) {
      if (this.currentFormIndex == fIdx) return;
      this.currentFormIndex = fIdx;
    },
    // 移动
    moveFn(index, move) {
      if (index == 0 && move == -1) {
        return;
      }
      if (index == this.jsonList.length - 1 && move == 1) {
        return;
      }
      const toIndex = index + move;
      this.jsonList[toIndex] = this.jsonList.splice(
        index,
        1,
        this.jsonList[toIndex]
      )[0];
      this.currentIndex = toIndex;
    },
    delFn(fIdx, idx) {
      this.$confirm("是否删除?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        this.formList[fIdx].jsonList.splice(idx, 1);
      });
    },
    /** 生成模板 **/
    genTemplate(formList) {
      let temp = "";
      formList.forEach((item) => {
        temp += this.genFormTemp(item.jsonList, item.functionName);
      });
      return temp;
    },
    /** 生成模板 **/
    genFormTemp(schema, functionName) {
      // 校验函数引入
      let validatorFunctions = [];
      let debounce = "";

      schema = schema.map((item) => {
        let params = {};
        this.formatCommon(item, params);
        // select类型
        if (["select", "radio", "checkbox", "cascader"].includes(item.type)) {
          let isDebounce = this.formatSelect(item, params, debounce);
          // debugger;
          if (isDebounce && !debounce) {
            debounce = `import debounce from 'lodash/debounce'`;
          }
        }
        // 上传类型
        if (item.type == "dstUpload") {
          this.formatDstUpload(item, params);
        }
        // 处理rules
        this.formatRules(item, validatorFunctions, params);
        return params;
      });
      /*
       * ^javascript('代码段')$  用于生成之前包裹动态部分，生成时会将其解除
       * 例如：
       * ^javascript('${item.validator}()')$ -----> checkSelect()
       * */
      let tempString = JSON.stringify(schema, null, "\t");
      tempString = tempString.replace(/"\^javascript\('(.*)'\)\$"/g, "$1");
      validatorFunctions = validatorFunctions.length
        ? `import { ${validatorFunctions.join(
            ","
          )} } from '@/portal-common/utils/testedFunc/formValidate'`
        : "";
      return `
${validatorFunctions}
${debounce}
export function ${functionName}(){
  return ${tempString}
}`;
    },
    // 导出
    exportFn() {
      const schema = this.genTemplate(cloneDeep(this.formList));
      console.log(schema);
      if (this.$vscode) {
        try {
          this.$vscode.postMessage({
            cmd: "genFile",
            params: {
              dir: this.exportForm.dir,
              fileName: this.exportForm.fileName + ".js",
              content: schema,
            },
          });
          this.exportDialogShow = false;
        } catch (e) {
          this.$message.error(e);
        }
      } else {
        // 文件下载
        let local = window.location.hostname == "localhost";
        if (local) {
          this.$message.info("调试模式不执行下载，控制台上查看输出结果");
        } else {
          const flieName = `form.js`;
          let link = document.createElement("a");
          link.download = flieName;

          var blob = new Blob([schema]);
          link.href = URL.createObjectURL(blob);

          document.body.appendChild(link);
          link.click();
          document.body.removeChild(link);
        }
      }
    },
    // 导入弹窗
    importShow(fIdx) {
      this.importDialogShow = true;
      this.currentFormIndex = fIdx;
      this.currentIndex = null;
    },
    // 导入
    importJson() {
      const { type, content } = this.importForm;
      const form = this.formList[this.currentFormIndex];
      if (type == "yapi") {
        let properties = {};
        try {
          let obj = JSON.parse(content);
          properties = obj.properties;
        } catch (e) {
          this.$message.error("解析失败");
          console.warn(e);
          return;
        }

        form.jsonList = Object.keys(properties).map((prop) =>
          this.generateItem({
            typeName: "input",
            prop: prop,
            name: properties[prop].description,
          })
        );
      } else {
        // 格式化json;
        let obj = [];
        try {
          obj = this.transDstData(content);
        } catch (e) {
          this.$message.error("解析失败");
          console.warn(e);
          return;
        }
        form.jsonList = obj;
      }

      this.handleCloseDialog();
    },
    handleCloseDialog() {
      this.exportDialogShow = false;
      this.importDialogShow = false;
    },
    // 转译dst格式数据
    transDstData(dstData) {
      let string = dstData
        .replace(/\r|\n|\s/g, "")
        .replace(/validator:(.*\((.*)\))/g, "validator:")
        .replace(/(\w*):/g, "$1:")
        .replace(/(,})/g, "}")
        .replace(/(,])/g, "]");
      console.log(string);
      let res = JSON.parse(dstData);
      return res;
    },
    // 类型模板
    typesMap(typeName) {
      const selectProperty = [
        {
          prop: "optionsDict",
          type: "input",
          value: "optionsDict",
          label: "选项字典",
        },
        {
          prop: "propsLabel",
          type: "input",
          value: "name",
          label: "选项Label",
        },
        { prop: "propsValue", type: "input", value: "id", label: "选项Value" },
        {
          prop: "propsDetail",
          type: "input",
          value: "Name",
          label: "选项翻译",
        },
        {
          prop: "remoteMethod",
          type: "input",
          label: "远程搜索",
        },
      ];

      let result = {};
      switch (typeName) {
        case "input":
        case "textarea":
          result.validateParams = this.validateMap("checkTitleLenth");
          break;
        case "select":
        case "radio":
        case "checkbox":
          result.validateParams = this.validateMap("checkSelect");
          result.selectProperty = selectProperty;
          break;
        case "cascader":
          result.validateParams = this.validateMap("checkSelect");
          result.selectProperty = selectProperty;
          result.props = { children: "childs" };
          break;
        case "dstUpload":
          result.validateParams = this.validateMap("checkSelect");
          result.uploadParams = [
            {
              prop: "listType",
              label: "展示方式",
              value: "text",
              type: "radio",
              options: [
                { name: "text", value: "text" },
                { name: "picture-card", value: "picture-card" },
              ],
            },
            {
              prop: "type",
              label: "文件类型",
              value: ["jpg", "jpeg", "png", "pdf", "xlsx"],
              type: "multiSelect",
              options: this.selectData.fileTypes,
            },
            { prop: "size", label: "文件大小(MB)", value: 50, type: "input" },
            { prop: "limit", label: "文件个数", value: 10, type: "input" },
          ];
          break;
        default:
          result.validateParams = this.validateMap("checkSelect");
          break;
      }

      return result;
    },
    // 校验属性模板
    validateMap(validatorName) {
      let validator = {
        prop: "validator",
        label: "校验规则",
        value: validatorName,
        type: "select",
        options: this.selectData.validateList,
        change: (e) => {
          this.currentItem.validateParams = this.validateMap(e);
        },
      };
      let result = [];
      switch (validatorName) {
        case "checkTitleLenth":
          result = [
            validator,
            { prop: "max", label: "最大长度", value: 30, type: "input" },
            { prop: "min", label: "最小长度", value: 0, type: "input" },
          ];
          break;
        case "rangeNumber":
          result = [
            validator,
            { prop: "max", label: "最大长度", value: 30, type: "input" },
            { prop: "min", label: "最小长度", value: 0, type: "input" },
            {
              prop: "isIntegerNumber",
              label: "是否只支持整数",
              value: false,
              type: "switch",
            },
          ];
          break;
        default:
          result = [validator];
          break;
      }
      return result;
    },
    /** 生成字段
     *  @params typeName      表单类型
     *  @params name          表单名称
     *  @params prop          字段名
     *  @params validator     校验函数
     *  @params required      是否必填
     *
     */
    generateItem({ typeName, name, prop, required }) {
      let emp = typeName + Math.floor(100 * Math.random());
      let params = {
        type: typeName,
        label: name || emp,
        commonParams: [
          {
            label: "类型",
            type: "select",
            prop: "type",
            value: typeName,
            options: this.selectData.typeList,
            change: (e) => {
              params.type = e;
              const { validateParams, selectProperty } = this.typesMap(e);
              params.validateParams = validateParams;
              if (selectProperty) {
                params.selectProperty = selectProperty;
              }
            },
          },
          {
            label: "字段名",
            type: "input",
            prop: "label",
            value: name || emp,
            change(e) {
              params.label = e;
            },
          },
          { label: "字段", type: "input", prop: "prop", value: prop || emp },
          {
            label: "展示方式",
            type: "radio",
            prop: "className",
            value: null,
            options: [
              { name: "两列式", value: null },
              { name: "一列式", value: "dst-block" },
            ],
          },
          {
            label: "是否必填",
            type: "switch",
            prop: "required",
            value: required || true,
          },
        ],
        ...this.typesMap(typeName),
      };
      return params;
    },
    // 生成模板-通用属性
    formatCommon(item, params) {
      item.commonParams.forEach((item) => {
        params[item.prop] = item.value;
      });

      // !params.className && delete params.className;
      return item;
    },
    // 生成模板-选择属性
    formatSelect(item, params) {
      let p = {};
      let isDebounce = false;
      item.selectProperty.forEach((item) => {
        p[item.prop] = item.value;
      });
      params.options = `^javascript('this.selectData.${String(
        p.optionsDict
      )} || []')$`;
      if (p.remoteMethod) {
        params.remote = true;
        params.remoteMethod = `^javascript('debounce(this.${String(
          p.remoteMethod
        )}, 300)')$`;
        isDebounce = true;
      }
      const commonProps = {
        label: p.propsLabel,
        value: p.propsValue,
        detailProp: p.propsDetail,
      };
      params.props = Object.assign({}, item.props, commonProps);

      return isDebounce;
    },
    // 生成模板-处理dstUpload
    formatDstUpload(item) {
      const params = {};
      item.uploadParams &&
        item.uploadParams.forEach((item) => {
          params[item.prop] = item.value;
        });
      item.listType = params.listType;
      item.limit = params.limit;
      item.filterOpts = { type: params.type.join(","), size: params.size };
      return item;
    },
    // 生成模板-处理rules
    formatRules(item, validatorFunctions, params) {
      let p = {
        required: params.required,
      };
      item.validateParams.forEach((item) => {
        p[item.prop] = item.value;
      });
      const validator = p.validator;

      delete p.validator;
      delete params.required;
      const validateParams = JSON.stringify(p)
        .replace(/"/g, "")
        .replace(/\s+/g, "");

      params.rules = {
        required: p.required || false,
        trigger: "change",
        validator: `^javascript('${validator}(${validateParams})')$`,
      };

      // 添加校验引入
      if (validator) {
        if (validatorFunctions.indexOf(validator) == -1) {
          validatorFunctions.push(validator);
        }
      }
      return item;
    },
    // 生成模板-删除多余字段
    excludeFields(item) {
      delete item.optionsDict;
      delete item.propsLabel;
      delete item.propsValue;
      delete item.propsDetail;
      delete item.required;

      delete item.commonParams;
      delete item.validateParams;
      delete item.uploadParams;
      !item.className && delete item.className;
    },
    // 清空
    clean() {
      this.formList = [
        {
          functionName: "dataArr",
          jsonList: [], // 子表单
        },
      ];
    },
    delForm(fIdx) {
      this.$confirm("是否删除?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        this.formList.splice(fIdx, 1);
      });
    },
  },
};
</script>

<style lang="scss" scoped>
.flex {
  display: flex;
}
.page {
  display: flex;
  width: 100%;
  margin-top: 20px;
  min-width: 1400px;
  height: calc(100% - 110px);
  & > div {
    margin: 0 5px;
  }
}
.type-bar {
  padding-top: 20px;
  width: 300px;
  // height: 100%;
  display: flex;
  flex-wrap: wrap;
  align-content: start;
  ::v-deep .el-card__body {
    padding: 0px;
  }
  .el-button {
    margin-left: 5px;
    margin-right: 5px;
    margin-bottom: 20px;
  }
}

.main {
  flex: 1;
  height: 100%;
  overflow-y: auto;
  .tool-bar {
    margin-bottom: 10px;
  }
  .main-form {
    // display: flex;
    // flex-wrap: wrap;
    text-align: left;
  }
  .main-card {
    margin-bottom: 15px;
    &.active {
      border-color: #66b1ff;
    }
    ::v-deep .el-card__body {
      padding: 10px;
    }
  }
}

.property-bar {
  width: 350px;
  height: 100%;
  overflow-y: auto;
  ::v-deep .el-card__body {
    padding: 0px;
  }
}
.input-items {
  display: inline-block;
  padding: 5px 5px 5px 14px;
  border: 1px dashed #bbb;
  margin: 0 2% 10px;
  position: relative;
  box-sizing: border-box;
  width: 44%;
  &.is-required::before {
    content: "*";
    color: #f56c6c;
    margin-left: -7px;
    position: absolute;
  }
  &.active {
    border: 1px solid rgb(64, 158, 255);
  }
  .label {
    line-height: 40px;
    margin-right: 10px;
  }
  .tools {
    position: absolute;
    right: 0;
    bottom: 0;
    color: #fff;
    background: rgb(64, 158, 255);
    > i {
      width: 20px;
      height: 20px;
      line-height: 20px;
      cursor: pointer;
    }
  }
  &.dst-block {
    width: 100%;
  }
}
.formHeader {
  display: flex;
  justify-content: space-between;
}
.formTitle {
  width: 200px;
  display: flex;
  justify-content: center;
  > span {
    display: inline-block;
    width: 120px;
    line-height: 40px;
  }
}
</style>
